///////////////////////////////////////////////////////////////////////////////
//
//  This source is part of the  "Managed Task Framework.NET Runtime"  (MTF.NET Runtime).
//
//
//  Copyright (c) 2012, Multicore Consulting Limited (UK)
//  All rights reserved.
//  http://www.multicoreconsulting.co.uk
//
//  Redistribution and use in source and binary forms, with or without
//  modification, are permitted provided that the following conditions
//  are met:
//
//  -  Redistributions of source code must retain the above copyright
//     notice, this list of conditions and the following disclaimer.
//  -  Redistributions in binary form must reproduce the above copyright
//     notice, this list of conditions and the following disclaimer in
//     the documentation and/or other materials provided with the
//     distribution.
//  -  Neither the name of Multicore Consulting Limited nor the names of
//     its contributors may be used to endorse or promote products
//     derived from this software without specific prior written
//     permission.
//
//  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
//  "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
//  LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
//  FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
//  COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
//  INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
//  BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
//  LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
//  CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
//  LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
//  ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
//  POSSIBILITY OF SUCH DAMAGE.
//
///////////////////////////////////////////////////////////////////////////////

using global::MTFOrg.MTF.MTFRuntime;
using global::MTFOrg.MTF.MTFCore;
using System.Threading;
using System;
using System.Collections;
using System.Collections.Generic;

namespace MTFOrg.MTF.MTFBlocks
{
    partial class Timer
    {
        private class TimerScheduler
        {
            /// <summary>
            /// Holds a request for a timer event.
            /// </summary>
            public struct TickRequest
            {
                private long m_ReqTime;

                public long ReqTime
                {
                    get { return m_ReqTime; }
                    set { m_ReqTime = value; }
                }
                private Action<long> m_Callback;

                public Action<long> Callback
                {
                    get { return m_Callback; }
                    set { m_Callback = value; }
                }

                public TickRequest(long reqTime, Action<long> callback)
                {
                    m_ReqTime = reqTime;
                    m_Callback = callback;
                }
            }

            /// <summary>
            /// The main timer thread.
            /// </summary>
            private Thread m_Thread;

            /// <summary>
            /// The list of current tick requests.
            /// </summary>
            private SortedList<long, List<TickRequest>> m_ReqList;

            /// <summary>
            /// Flag to indicate shutdown.
            /// </summary>
            private bool m_IsShutdown;

            /// <summary>
            /// Private constructor to enforce singleton.
            /// </summary>
            private TimerScheduler()
            {
                m_IsShutdown = false;

                m_ReqList = new SortedList<long, List<TickRequest>>();
            }

            /// <summary>
            /// Singleton instance.
            /// </summary>
            private static TimerScheduler m_Instance;

            /// <summary>
            /// Gets the singleton instance.
            /// </summary>
            public static TimerScheduler Instance
            {
                get
                {
                    if (m_Instance == null)
                        m_Instance = new TimerScheduler();

                    return m_Instance;
                }
            }

            /// <summary>
            /// Registers a request for a tick event at a specified time.
            /// </summary>
            /// <param name="reqTime"></param>
            /// <param name="callback"></param>
            public void RegisterTickRequest(long reqTime, Action<long> callback)
            {
                if (!m_ReqList.ContainsKey(reqTime))
                {
                    m_ReqList.Add(reqTime, new List<TickRequest>());
                }
                m_ReqList[reqTime].Add(new TickRequest(reqTime, callback));

            }

            public void Start()
            {
                if (m_Thread == null)
                {
                    m_Thread = new Thread(ThreadFunction);
                    m_Thread.Start();
                }
            }

            /// <summary>
            /// Called to shut down the timer scheduler.
            /// </summary>
            public void Shutdown()
            {
                m_IsShutdown = true;
                m_Thread.Join();
            }

            /// <summary>
            /// Called to run the timer thread code.
            /// </summary>
            private void ThreadFunction()
            {
                while (!m_IsShutdown)
                {
                    long now = DateTime.Now.Ticks;

                    // Process all the requests that have expired
                    List<TickRequest> oldestReqs;
                    while (m_ReqList.Count > 0)
                    {
                        oldestReqs = m_ReqList.Values[0];

                        if (m_ReqList.Keys[0] > now)
                        {
                            // This is in the future so save it for later
                            break;
                        }

                        m_ReqList.RemoveAt(0);

                        // Action all the requests for this time
                        foreach (TickRequest oldestReq in oldestReqs)
                        {
                            oldestReq.Callback(oldestReq.ReqTime);
                        }
                    }

                    // Wait for the next tick
                    if (m_ReqList.Count > 0)
                    {
                        // If there are future tick requests then wait for the next one
                        int waitMs = (int)((m_ReqList.Keys[0] - now) / 10000);
                        if (waitMs < 10) waitMs = 10;
                        Thread.Sleep(waitMs);
                    }
                    else
                    {
                        // No requests pending so sleep for a bit
                        Thread.Sleep(100);
                    }
                }
            }
        }

        /// <summary>
        /// Called to perform single-threaded initialization.
        /// </summary>
        public override void OnInitialize()
        {
            base.OnInitialize();

            // Register for shutdown notification
            CoreTaskManager.OnPreShutdown += new ThreadPoolTaskManager.PreShutdownDelegate(CoreTaskManager_OnPreShutdown);
        }

        /// <summary>
        /// Called prior to shutdown.
        /// </summary>
        void CoreTaskManager_OnPreShutdown()
        {
            // Wait until the timer is not active
            TimerScheduler.Instance.Shutdown();
        }

        /// <summary>
        /// Called when everything is set up and execution can begin.
        /// </summary>
        public override void OnBeginExecution()
        {
            base.OnBeginExecution();

            // Request an initial event
            TimerScheduler.Instance.RegisterTickRequest(DateTime.Now.Ticks, TimerCallback);
            TimerScheduler.Instance.Start();
        }

        /// <summary>
        /// Callback invoked by the timer scheduler.
        /// </summary>
        /// <param name="reqTime"></param>
        private void TimerCallback(long reqTime)
        {
            // Request the next event to be sent after Period milliseconds
            TimerScheduler.Instance.RegisterTickRequest(DateTime.Now.Ticks + Period * 10000, TimerCallback);

            if (Enabled)
            {
                // Generate MTF event containing the tick time
                EventDictionary timerEvent = (EventDictionary)m_TickWriteTask.Request(0);
                if (timerEvent != null)
                {
                    QueueEvent timerQueueEvent = (QueueEvent)timerEvent["TickWrite"][0];
                    timerQueueEvent.Item = reqTime;
                    m_TickWriteTask.Release(0);
                }
            }
        }
    }
}